/* 
 *	Copyright (C) 2009-2010 miaohua
 *	http://code.google.com/p/lrcshower/
 *
 *  This Program is free software; you can redistribute it and/or modify
 *  it under the terms of the GNU General Public License as published by
 *  the Free Software Foundation; either version 2, or (at your option)
 *  any later version.
 *   
 *  This Program is distributed in the hope that it will be useful,
 *  but WITHOUT ANY WARRANTY; without even the implied warranty of
 *  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
 *  GNU General Public License for more details.
 *   
 *  You should have received a copy of the GNU General Public License
 *  along with GNU Make; see the file COPYING.  If not, write to
 *  the Free Software Foundation, 675 Mass Ave, Cambridge, MA 02139, USA. 
 *  http://www.gnu.org/copyleft/gpl.html
 *
 *
 *
 * I combine both Adrian Stanley's DragDropListCtrl(adrian@adrians.demon.co.uk)
 * and Stuart Carter's CFileDropListCtrl(stuart.carter@hotmail.com)
 * Thanks to both of them
*/

#include "StdAfx.h"
#include <sys/stat.h>
#include "ListCtrlEx.h"
#include "HelperFunctions.h"
#include "MusicBoxSetting.h"

#ifdef _DEBUG
#define new DEBUG_NEW
#undef THIS_FILE
static char THIS_FILE[] = __FILE__;
#endif


extern MusicBoxSetting Gbl_AppProp;

static const int DROPITEM_SCROLL_TIMER_ID = 1;
static const int DROPFILE_SCROLL_TIMER_ID = 2;

IMPLEMENT_DYNAMIC(CListCtrlEx,CListCtrl)

CListCtrlEx::CListCtrlEx() :
	m_DropIndex(-1),
	m_DragImage(NULL),
	m_PrevDropIndex(-1),
	m_PrevDropState(NULL),
	m_ScrollTimer(NULL),
	m_ScrollDirection(ScrollNull),
	m_ListStyle(NULL),
    m_EnableTips(FALSE),
    m_TipsPreItemIdx(-1),
    m_FileDragPreItemIdx(-1),
    m_CurSongIdx(-1)
{
     m_CtrlMode.m_DropMask = DL_ACCEPT_FILES|DL_ALLOW_DUPLICATES;
     m_CtrlMode.m_DropFileCallback = NULL;
     m_CtrlMode.m_DropItemCallback = NULL;
     m_CtrlMode.m_GetTipCallback = NULL;
     m_CtrlMode.m_DropItemFinishCallback = NULL;
     m_CtrlMode.m_DropFileStartCallback = NULL;
     m_CtrlMode.m_DropFileFinishCallback = NULL;

     m_PlayingTextColor = RGB(0,255,0);             //playing song 's color of the text
     m_PlayingTextBgColor = RGB(10,10,10);          //playing song 's color of the text background

     ::OleInitialize(NULL);
}

CListCtrlEx::~CListCtrlEx()
{
     // Fail safe clean up.
     delete m_DragImage;
     //KillScrollTimer(DROPFILE_SCROLL_TIMER_ID);
     ::OleUninitialize();
}


BEGIN_MESSAGE_MAP(CListCtrlEx, CListCtrl)
	//{{AFX_MSG_MAP(CListCtrlEx)
	ON_WM_CREATE()
	ON_WM_DESTROY()
	ON_WM_MOUSEMOVE()
	ON_WM_LBUTTONUP()
	ON_NOTIFY_REFLECT(LVN_BEGINDRAG, OnBeginDrag)
    ON_NOTIFY_REFLECT(NM_CUSTOMDRAW, OnCustDraw)
	ON_WM_TIMER()
	//}}AFX_MSG_MAP
END_MESSAGE_MAP()

/////////////////////////////////////////////////////////////////////////////
// CListCtrlEx message handlers
int CListCtrlEx::OnCreate(LPCREATESTRUCT lpCreateStruct) 
{
	if( CListCtrl::OnCreate(lpCreateStruct) == -1 )
		return -1;
	
	//
	// Register ourselves as a drop target for files
	//
	//DragAcceptFiles(TRUE);
    m_FilesDropDrop.Register(this);
	
	return 0;
}

void CListCtrlEx::OleInitialize()
{
     m_FilesDropDrop.Register(this);
}

// CListCtrlEx message handlers
void CListCtrlEx::OnCustDraw(NMHDR *pNotifyStruct, LRESULT *pResult)
{
     LPNMLVCUSTOMDRAW lvcd = (LPNMLVCUSTOMDRAW) pNotifyStruct; // cast our generic structure to bigger/specialized strc
     NMCUSTOMDRAW  &nmcd = lvcd->nmcd;

     int iRow;
     switch(nmcd.dwDrawStage)
     {

           case CDDS_PREPAINT:
                *pResult  = CDRF_NOTIFYITEMDRAW|CDRF_NOTIFYSUBITEMDRAW;	// request notifications for individual items
                break;

           case CDDS_ITEMPREPAINT:
                *pResult = CDRF_NOTIFYSUBITEMDRAW;
                break;
           case CDDS_ITEMPREPAINT|CDDS_SUBITEM:	// drawing subitem
                iRow = nmcd.dwItemSpec;	
                if( iRow == m_CurSongIdx )
                {
                    lvcd->clrText   = m_PlayingTextColor;    // Use my foreground hilite color
                    lvcd->clrTextBk = m_PlayingTextBgColor;  // Use my background hilite color
                }
                *pResult = CDRF_NEWFONT;
                break;
           default:
                *pResult = CDRF_DODEFAULT;
                break;
     }
}

void CListCtrlEx::SetPlayTextClr(COLORREF Color)
{
     m_PlayingTextColor = Color;
}

void CListCtrlEx::SetPlayTextBgClr(COLORREF Color)
{
     m_PlayingTextBgColor = Color;
}

void CListCtrlEx::SetCurSongIdx(int Idx)
{
     m_CurSongIdx = Idx;
     RedrawWindow(NULL,NULL,RDW_FRAME | RDW_INVALIDATE | RDW_ERASE | RDW_UPDATENOW);
}

void CListCtrlEx::OnDropFiles(HDROP DropInfo, CPoint Point)
{
     // Get the number of pathnames (files or folders) dropped
     UINT NumFilesDropped = DragQueryFile(DropInfo, 0xFFFFFFFF, NULL, 0);

     // Iterate through the pathnames and process each one
     TCHAR FileName[MAX_PATH + 1];
     CString PathName;
     CString csExpandedFilename;

     // Get Mouse postion
     int RowIdx = HitTest(Point);
     int InsertPos;
     if( RowIdx >= 0 )
         InsertPos = GetItemData(RowIdx);
     else
         InsertPos = -1;
     
     // Remove all selected rows
     ClearAllSel();
    
     //if( m_CtrlMode.m_DropFileStartCallback )
     //    m_CtrlMode.m_DropFileStartCallback(this, NumFilesDropped);
     
     for( UINT i = 0 ; i < NumFilesDropped; ++i )
     {
	      // Get the pathname
	      DragQueryFile(DropInfo, i, FileName, _countof(FileName));

	      // It might be shortcut, so try and expand it(I do not do it here, right now)
	      PathName = FileName;

	      // Do check
          unsigned int PathType = 0;
          if( ValidatePathName(PathName, PathType) )
          {
              DropFilesParam Param;
              //if( RowIdx >= 0 )
              //    Param.m_UserData = GetItemData(RowIdx);
              //else
              //    Param.m_UserData = -1;
              Param.m_UserData = ++InsertPos;
              Param.m_FullPath = (LPCTSTR)PathName;
              Param.m_RowIdx = ++RowIdx;
              // Callbak
	          if( (m_CtrlMode.m_DropMask & DL_ADD_CALLBACK) && m_CtrlMode.m_DropFileCallback )
		          m_CtrlMode.m_DropFileCallback(this, PathName, &Param);

              // Insert file item
	 	      //InsertPathName(&Param);
          }
     }

     // Free the dropped-file info that was allocated by windows
     DragFinish(DropInfo);
          

     // Do finish callback if needed
     //if( NumFilesDropped>0 && m_CtrlMode.m_DropFileFinishCallback )
     //    m_CtrlMode.m_DropFileFinishCallback(this, RowIdx, GetItemCount()-1, NumFilesDropped);

     //SetItemCountEx(GetItemCount()+NumFilesDropped);
     //Invalidate();

}


void CListCtrlEx::OnBeginDrag(NMHDR* pNMHDR, LRESULT* pResult) 
{
     NM_LISTVIEW* pNMListView = (NM_LISTVIEW*)pNMHDR;

     if( pNMListView )
     {
	     m_PrevDropIndex	= -1;
	     m_PrevDropState	= NULL;
     	
	     // Items being dragged - can be one or more.
	     m_DragSelIdxes.RemoveAll();
         // Get selected items' index
	     POSITION Pos = GetFirstSelectedItemPosition();
	     while(Pos)
		     m_DragSelIdxes.Add(GetNextSelectedItem(Pos));
     	
         // If single sel is set,remove it. we need multi sel attribution when
         // paint the target item while dragging
	     DWORD Style = GetStyle();
	     if ((Style & LVS_SINGLESEL) == LVS_SINGLESEL)
	     {
		     m_ListStyle = Style;
		     ModifyStyle(LVS_SINGLESEL, NULL);
	     }

	     if( m_DragSelIdxes.GetSize() > 0 )
	     {
		     // Create a drag image from the centre point of the item image.
		     // Clean up any existing drag image first.
		     delete m_DragImage;
		     CPoint PtDragItem;
             // Create image to contain all selected items
		     m_DragImage = CreateDragImageEx(&PtDragItem);
		     if( m_DragImage )
		     {
			     m_DragImage->BeginDrag(0, PtDragItem);
			     m_DragImage->DragEnter(CWnd::GetDesktopWindow(), pNMListView->ptAction);
     		
			     // Capture all mouse messages in case the user drags outside the control.
			     SetCapture();
	         }
	     }
     }

     *pResult = 0;
}

// Based on code by Frank Kobs.
CImageList* CListCtrlEx::CreateDragImageEx(LPPOINT OffsetPt)
{
     CRect RectSingle;	
     CRect RectComplete;
     bool First = true;

     // Determine the size of the drag image(contains all selected items)
     POSITION Pos = GetFirstSelectedItemPosition();
     while(Pos)
     {
	     int Idx = GetNextSelectedItem(Pos);
	     GetItemRect(Idx, RectSingle, LVIR_BOUNDS);
	     if(First)
	     {
		     // Initialize the CompleteRect
		     GetItemRect(Idx, RectComplete, LVIR_BOUNDS);
		     First = false;
	     }
	     RectComplete.UnionRect(RectComplete, RectSingle);
     }

     // Create bitmap in memory DC
     CClientDC DcClient(this);	
     CDC DcMem;	
     CBitmap DragBmp;

     if( !DcMem.CreateCompatibleDC(&DcClient) )
	     return NULL;

     if( !DragBmp.CreateCompatibleBitmap(&DcClient, RectComplete.Width(), RectComplete.Height()))
	     return NULL;
	
     // Select into memory DC
     CBitmap* OldMemDcBmp = DcMem.SelectObject(&DragBmp);
     // Here green is used as mask color.
     DcMem.FillSolidRect(0, 0, RectComplete.Width(), RectComplete.Height(), RGB(0, 255, 0)); 

     // Paint each DragImage in the DC.
     CImageList* SingleImageList = NULL;
     CPoint ItemPt;

     Pos = GetFirstSelectedItemPosition();
     while(Pos)
     {
	     int Idx = GetNextSelectedItem(Pos);
	     GetItemRect(Idx, RectSingle, LVIR_BOUNDS);

	     SingleImageList = CreateDragImage(Idx, &ItemPt);
	     if( SingleImageList )
	     {
		     // Make sure width takes into account not using LVS_EX_FULLROWSELECT style.
		     IMAGEINFO ImageInfo;
		     SingleImageList->GetImageInfo(0, &ImageInfo);
		     RectSingle.right = RectSingle.left + (ImageInfo.rcImage.right - ImageInfo.rcImage.left);

             // Draw it
		     SingleImageList->DrawIndirect(
			     &DcMem, 
			     0, 
			     CPoint(RectSingle.left - RectComplete.left, 
			     RectSingle.top - RectComplete.top),
			     RectSingle.Size(), 
			     CPoint(0,0));

		     delete SingleImageList;
	     }
     }

     DcMem.SelectObject(OldMemDcBmp);

     // Create the imagelist	with the merged drag images.
     CImageList* CompleteImageList = new CImageList;
     CompleteImageList->Create(RectComplete.Width(), RectComplete.Height(), ILC_COLOR|ILC_MASK, 0, 1);
     // Here green is used as mask color.
     CompleteImageList->Add(&DragBmp, RGB(0, 255, 0)); 
     // Destroy drag bmp
     DragBmp.DeleteObject();

     // As an optional service:
     // Find the offset of the current mouse cursor to the imagelist
     // this we can use in BeginDrag().
     if( OffsetPt )
     {
	     CPoint PtCursor;
	     GetCursorPos(&PtCursor);
	     ScreenToClient(&PtCursor);
	     OffsetPt->x = PtCursor.x - RectComplete.left;
	     OffsetPt->y = PtCursor.y - RectComplete.top;
     }

     return CompleteImageList;
}

void CListCtrlEx::OnMouseMove(UINT nFlags, CPoint Point) 
{
     // Within drag & drop
     if( m_DragImage )
     {
	     // Must be dragging, as there is a drag image.

	     // Move the drag image.
		 CPoint PtDragImage(Point);
		 ClientToScreen(&PtDragImage);
		 m_DragImage->DragMove(PtDragImage);
		
		 // Leave dragging so we can update potential drop target selection.
		 m_DragImage->DragLeave(CWnd::GetDesktopWindow());

		 // Force x coordinate to always be in list control - only interested in y coordinate.
		 // In effect the list control has captured all horizontal mouse movement.
		 static const int nXOffset = 8;
		 CRect Rect;
		 GetWindowRect(Rect);
         // Get the window where the mouse is
		 CWnd* DropWnd = CWnd::WindowFromPoint(CPoint(Rect.left + nXOffset, PtDragImage.y));

		 // Get the window under the drop point.
		 if( DropWnd == this )
		 {
			 // Still in list control so select item under mouse as potential drop target.
			 Point.x = nXOffset;	// Ensures x coordinate is always valid(in the list ctrl)
             // Highlight the potential drop target item
			 UpdateSelection(HitTest(Point));
		 }

		 CRect ClientRect;
		 GetClientRect(ClientRect);
		 CPoint PtClientDragImage(PtDragImage);
		 ScreenToClient(&PtClientDragImage);

		 // Client rect includes header height, so ignore it, i.e.,
		 // moving the mouse over the header (and higher) will result in a scroll up.
		 CHeaderCtrl* ListHeader = (CHeaderCtrl*)GetDlgItem(0);
		 if( ListHeader )
		 {
		 	 CRect HeaderRect;
		 	 ListHeader->GetClientRect(HeaderRect);
			 ClientRect.top += HeaderRect.Height();
		 }
           
		 if( PtClientDragImage.y < ClientRect.top )
		 {
			 // Mouse is above the list control - scroll up.
			 //SetScrollTimer(ScrollUp,DROPITEM_SCROLL_TIMER_ID);
             m_ScrollDirection = ScrollUp;
             OnTimer(DROPITEM_SCROLL_TIMER_ID);
		 } else if (PtClientDragImage.y > ClientRect.bottom) {
			 // Mouse is below the list control - scroll down.
			 // SetScrollTimer(ScrollDown,DROPITEM_SCROLL_TIMER_ID);
             m_ScrollDirection = ScrollDown;
             OnTimer(DROPITEM_SCROLL_TIMER_ID);
		 }
 
		 // Resume dragging.
		 m_DragImage->DragEnter(CWnd::GetDesktopWindow(), PtDragImage);
     } 
   
     // Wether to show tips
     if( m_EnableTips )
     {
         // Decide which row the mouse is
         LVHITTESTINFO Lvhti;
     	 Lvhti.pt = Point;	
         HitTest(&Lvhti);
         // Do action the mouse in another row
         if( Lvhti.iItem != m_TipsPreItemIdx )
	     { 
             // Save the postion
		     m_TipsPreItemIdx = Lvhti.iItem;
		     
             // Wether it is a validate row
		     if( m_TipsPreItemIdx != -1 )
		     {
                 tstring Tips;
			     if( m_CtrlMode.m_GetTipCallback )
                 {
                     unsigned int SongIdx = GetItemData(m_TipsPreItemIdx);
                     m_CtrlMode.m_GetTipCallback(this, SongIdx, Tips);
                 }
			     m_ToolTip.AddTool(this, Tips.c_str());
			     m_ToolTip.Pop();
		     } else {
			     m_ToolTip.AddTool(this, _T(""));
			     m_ToolTip.Pop();
		     }
	     }
     }

     CListCtrl::OnMouseMove(nFlags, Point);
}

void CListCtrlEx::UpdateSelection(int DropIndex)
{
	if( DropIndex > -1 && DropIndex < GetItemCount())
	{
		// Drop index is valid and has changed since last mouse movement.
		RestorePrevDropItemState();

		// Save information about current potential drop target for restoring next time round.
		m_PrevDropIndex = DropIndex;
		m_PrevDropState = GetItemState(DropIndex, LVIS_SELECTED);

		// Select current potential drop target.
		SetItemState(DropIndex, LVIS_SELECTED, LVIS_SELECTED);
		m_DropIndex = DropIndex;		// Used by DropItem().

        // Redraw
		UpdateWindow();
	}
}

void CListCtrlEx::RestorePrevDropItemState()
{
	if( m_PrevDropIndex > -1 && m_PrevDropIndex < GetItemCount() )
	{
		// Restore state of previous potential drop target.
		SetItemState(m_PrevDropIndex, m_PrevDropState, LVIS_SELECTED);
	}
}


void CListCtrlEx::SetScrollTimer(EScrollDirection ScrollDirection, int TimerId)
{
     if( !m_ScrollTimer )
         m_ScrollTimer = SetTimer(TimerId, 50, NULL);

     m_ScrollDirection = ScrollDirection;
}

void CListCtrlEx::KillScrollTimer(int TimerId)
{
     if( m_ScrollTimer )
     {
	     KillTimer(TimerId);
	     m_ScrollTimer = NULL;
	     m_ScrollDirection = ScrollNull;
     }
}

void CListCtrlEx::OnLButtonUp(UINT nFlags, CPoint point) 
{
     if( m_DragImage )
     {
	     // Release the mouse capture and end the dragging.
	     ReleaseCapture();
	     m_DragImage->DragLeave(CWnd::GetDesktopWindow());
	     m_DragImage->EndDrag();

	     delete m_DragImage;
	     m_DragImage = NULL;

	     // Drop the item on the list.
	     DropItem();
     }

     CListCtrl::OnLButtonUp(nFlags, point);
}

void CListCtrlEx::DropItem()
{
     // Restore previous drop index to be unselected state
     RestorePrevDropItemState();

     // Drop after currently selected item.
     ++m_DropIndex;
     if( m_DropIndex < 0 || m_DropIndex > GetItemCount() - 1)
     {
	     // Fail safe - invalid drop index, so drop at end of list.
	     m_DropIndex = GetItemCount();
     }
     int StartPosIdx = m_DropIndex;

     // Remove all selected rows
     ClearAllSel();

     // Get columns number
     int Columns = 1;
     CHeaderCtrl* Header = (CHeaderCtrl*)GetDlgItem(0);
     if( Header )
	     Columns = Header->GetItemCount();

     int MinDragIdx = GetItemCount()+10;
     int MaxDragIdx = -1;
     // Move all dragged items to their new positions.
     for( int DragItem = 0; DragItem < m_DragSelIdxes.GetSize(); ++DragItem)
     {
	      int DragIndex = m_DragSelIdxes[DragItem];
	      if( DragIndex > -1 && DragIndex < GetItemCount() )
	      {
              // Record the min & max drag index
              if( MinDragIdx > DragIndex )
                  MinDragIdx = DragIndex;
              if( MaxDragIdx < DragIndex )
                  MaxDragIdx = DragIndex;

		      // Get information about this drag item.
              DropItemsParam Params;
              if( m_CtrlMode.m_DropItemCallback )
			      m_CtrlMode.m_DropItemCallback(this, DragIndex, m_DropIndex, &Params);
            
              if( Params.m_ColumVals.size() > 0 )
              {
                  // Insert one
                  InsertItem(LVIF_TEXT | LVIF_STATE, m_DropIndex, 
                             Params.m_ColumVals[0].c_str(), 
                             0, LVIS_SELECTED, 0, 0);
			      // Fill in all the columns for the dragged item.
			      for( int i = 1; i < Columns; ++i )
                       SetItem(m_DropIndex,i,LVIF_TEXT,Params.m_ColumVals[i].c_str(),0,0,0,0,0);			
                  // Set user data
                  SetItemData(m_DropIndex, Params.m_UserData);
                  // Set the new row be selected status
			      SetItemState(m_DropIndex, LVIS_SELECTED, LVIS_SELECTED);
              }

			  // Index of dragged item will change if item has been dropped above itself.
			  if( DragIndex > m_DropIndex )
				  ++DragIndex;
			  // Delete the original item.
			  DeleteItem(DragIndex);

			  // Need to adjust indexes of remaining drag items.
			  for( int i = DragItem+1; i < m_DragSelIdxes.GetSize(); ++i)
			  {
				   int NewDragIndex = m_DragSelIdxes[i];

				   if( DragIndex < NewDragIndex && NewDragIndex < m_DropIndex)
				   {
					   // Item has been removed from above this item, and inserted after it,
					   // so this item moves up the list.
					   m_DragSelIdxes[i] = max(NewDragIndex - 1, 0);
				   } else if (DragIndex > NewDragIndex && NewDragIndex > m_DropIndex){
					   // Item has been removed from below this item, and inserted before it,
					   // so this item moves down the list.
					   m_DragSelIdxes[i] = NewDragIndex + 1;
				   }
			  }

			  if( DragIndex > m_DropIndex )
			  {
				  // Item has been added before the drop target, so drop target moves down the list.
				  ++m_DropIndex;
			  }
		}
	}

    
     // Restore the list style(it is modified in OnBeginDrag)
	 //if( m_ListStyle != NULL )
	 //{
		// // Style was modified, so return it back to original style.
		// ModifyStyle(NULL, m_ListStyle);
		// m_ListStyle = NULL;
	 //}

     // Do finish callback if needed 
     if( m_CtrlMode.m_DropItemFinishCallback )
         if( StartPosIdx <= MinDragIdx )
             m_CtrlMode.m_DropItemFinishCallback(this, StartPosIdx, MaxDragIdx);
         else
             m_CtrlMode.m_DropItemFinishCallback(this, MinDragIdx, MaxDragIdx);

     // Invalidate & Redraw
     Invalidate();
     UpdateWindow();
}

void CListCtrlEx::OnTimer(UINT IDEvent) 
{
     if( IDEvent == DROPITEM_SCROLL_TIMER_ID && m_DragImage)
     {
	     WPARAM wParam	= NULL;
	     int DropIndex= -1;
	     if( m_ScrollDirection == ScrollUp )
	     {
		     wParam		= MAKEWPARAM(SB_LINEUP, 0);
		     DropIndex	= m_DropIndex - 1;
	     }
	     else if ( m_ScrollDirection == ScrollDown )
	     {
		     wParam		= MAKEWPARAM(SB_LINEDOWN, 0);
		     DropIndex	= m_DropIndex + 1;
	     }
	     m_DragImage->DragShowNolock(FALSE);
	     SendMessage(WM_VSCROLL, wParam, NULL);
	     UpdateSelection(DropIndex);
	     m_DragImage->DragShowNolock(TRUE);
     } else if( IDEvent == DROPFILE_SCROLL_TIMER_ID ) {
	     WPARAM wParam	= NULL;
	     if( m_ScrollDirection == ScrollUp )
		     wParam		= MAKEWPARAM(SB_LINEUP, 0);
	     else if ( m_ScrollDirection == ScrollDown )
		     wParam		= MAKEWPARAM(SB_LINEDOWN, 0);
	     SendMessage(WM_VSCROLL, wParam, NULL);
     }

     CListCtrl::OnTimer(IDEvent);
}

bool CListCtrlEx::SetCtrlMode(const LISTMODE & CtrlMode)
{
	// If they want to use a callback, ensure they also
	// specified the address of a function...
	if( ((CtrlMode.m_DropMask&DL_ADD_CALLBACK) && CtrlMode.m_DropFileCallback == NULL)
      ||((CtrlMode.m_DropMask&DL_TIP_CALLBACK) && CtrlMode.m_GetTipCallback == NULL) )
	{
		// Must specify a function if using DL_ADD_CALLBACK and DL_TIP_CALLBACK flag
		ASSERT(FALSE);
		return false;
	}

	m_CtrlMode = CtrlMode;
	return true;
}

bool CListCtrlEx::ValidatePathName(const CString& PathName, unsigned int & PathType) const
{
	struct _stat FileStat;
	int Res = _tstat(PathName, &FileStat);
	if( !Res )
	{
		if( (m_CtrlMode.m_DropMask & DL_ACCEPT_FOLDERS) &&
            (FileStat.st_mode & _S_IFDIR) == _S_IFDIR ) 
	    {
			PathType = DL_FOLDER_TYPE;
            return true;
		} 
	    else if( (m_CtrlMode.m_DropMask & DL_ACCEPT_FILES) &&
				 ((FileStat.st_mode & _S_IFREG) == _S_IFREG) ) 
	    {
			// We've got a file and files are allowed.
			PathType = DL_FILE_TYPE;

			// Now if we are filtering out specific types,
			// check the file extension.
			if( m_CtrlMode.m_DropMask & DL_FILTER_EXTENSION )
			{
                // We just compare in lower case
                tstring FileExt = PathFindExtension(PathName);
                ToLower(FileExt);
                if( CheckFileExt(m_CtrlMode.m_FileExt, FileExt) )
                    return true;
			}
			
            return false;
		}
	}

	return false;
}

int CListCtrlEx::InsertPathName(const DropFilesParam * Params)
{
	if( !(m_CtrlMode.m_DropMask & DL_ALLOW_DUPLICATES) )
	{
		// We don't allow duplicate pathnames, so
		// see if this one is already in the list.
		LVFINDINFO lvInfo;
		lvInfo.flags = LVFI_STRING;
		lvInfo.psz = Params->m_FullPath.c_str();

		if(FindItem(&lvInfo, -1) != -1)
			return -1;
	}
    
    if( Params->m_ColumVals.size() > 0 )
    {
		InsertItem(LVIF_TEXT | LVIF_STATE, Params->m_RowIdx, 
                   Params->m_ColumVals[0].c_str(), 
                   0, LVIS_SELECTED, 0, 0);

		// Fill in all the columns for the dragged item.
        unsigned int Columns = Params->m_ColumVals.size();
		for( unsigned int i = 1; i < Columns; ++i )
             SetItem(Params->m_RowIdx,i,LVIF_TEXT,Params->m_ColumVals[i].c_str(),0,0,0,0,0);

        // Set user data(the song index)
        SetItemData(Params->m_RowIdx, Params->m_UserData);

        // Set to be selected status
        SetItemState(Params->m_RowIdx, LVIS_SELECTED, LVIS_SELECTED);
        return 1;        
    } else
        return InsertItem(Params->m_RowIdx, Params->m_FullPath.c_str());
}

bool CListCtrlEx::CheckFileExt(const tstring & ExtList, const tstring & Ext)const
{
     tstring::size_type Idx = ExtList.find_first_of(';');
     tstring::size_type StartIdx = 0;
     tstring SubExt;
     while( Idx != tstring::npos )
     {
            SubExt = ExtList.substr(StartIdx, Idx);
            if( SubExt == Ext ) return true;

            StartIdx = Idx+1;
            Idx = ExtList.find_first_of(';');
     }  

     if( ExtList.substr(StartIdx) == Ext ) return true;
     else return false;
}


BOOL CListCtrlEx::EnableTips()
{
     EnableToolTips(TRUE);
     m_EnableTips = m_ToolTip.Create(this, TTS_ALWAYSTIP);

     if( m_EnableTips )
     {
         m_ToolTip.Activate(TRUE);
         m_ToolTip.SetDelayTime(TTDT_INITIAL, 0);
         // Sets the maximum width for a ToolTip window
         m_ToolTip.SendMessage(TTM_SETMAXTIPWIDTH, 0, (LPARAM)(INT)400);
     }

     return m_EnableTips;
}

BOOL CListCtrlEx::PreTranslateMessage(MSG* pMsg) 
{
     if( ::IsWindow(m_ToolTip.GetSafeHwnd()) )
         m_ToolTip.RelayEvent(pMsg);

     return CListCtrl::PreTranslateMessage(pMsg);
}

DROPEFFECT CListCtrlEx::OnDragEnter(CWnd* pWnd, COleDataObject* pDataObject, DWORD dwKeyState, CPoint point)
{
     return DROPEFFECT_COPY;
}

DROPEFFECT CListCtrlEx::OnDragOver(CWnd* pWnd, COleDataObject* pDataObject, DWORD dwKeyState, CPoint Point)
{
     int Idx = HitTest(Point); 
     if( Idx != -1 && m_FileDragPreItemIdx != Idx )
     {
         // Get current potential target
         CRect RectSingle;	
	     GetItemRect(Idx, RectSingle, LVIR_BOUNDS);

         // Redraw
         Invalidate();
         UpdateWindow();

         // Draw the potential target
         CClientDC DcClient(this);	
         DcClient.FillSolidRect(0, RectSingle.bottom, RectSingle.Width(), 2, RGB(0, 0, 255));

         m_FileDragPreItemIdx = Idx;
     }

  
     CRect ClientRect;
     GetClientRect(ClientRect);

     // Client rect includes header height, so ignore it, i.e.,
     // moving the mouse over the header (and higher) will result in a scroll up.
     CHeaderCtrl* ListHeader = (CHeaderCtrl*)GetDlgItem(0);
     if( ListHeader )
     {
	     CRect HeaderRect;
	     ListHeader->GetClientRect(HeaderRect);
	     ClientRect.top += HeaderRect.Height();
     }

     if( Point.y < ClientRect.top )
     {
         WPARAM wParam = MAKEWPARAM(SB_LINEUP, 0);
         SendMessage(WM_VSCROLL, wParam, NULL);
     } else if (Point.y > ClientRect.bottom) {
         WPARAM wParam = MAKEWPARAM(SB_LINEDOWN, 0);
         SendMessage(WM_VSCROLL, wParam, NULL);
     }

     return DROPEFFECT_COPY;
}

DROPEFFECT CListCtrlEx::OnDropEx(CWnd* pWnd, COleDataObject* pDataObject, DROPEFFECT dropEffect, DROPEFFECT dropList, CPoint point)
{
     return -1; // Indicates framework should call OnDrop
}

BOOL CListCtrlEx::OnDrop(CWnd* pWnd, COleDataObject* pDataObject, DROPEFFECT DropEffect, CPoint Point)
{
     if( pDataObject->IsDataAvailable(CF_HDROP) )
     {
         HGLOBAL MemData = pDataObject->GetGlobalData(CF_HDROP);
         HDROP DropFiles = (HDROP)GlobalLock((HGLOBAL)MemData);
         OnDropFiles(DropFiles, Point);
         GlobalUnlock(MemData);
         return TRUE;
     } else
         return FALSE;
}

void CListCtrlEx::OnDragLeave(CWnd* pWnd)
{
     //Invalidate();
     //UpdateWindow();
}

void CListCtrlEx::OnDestroy() 
{
     CListCtrl::OnDestroy();

     m_ToolTip.DestroyWindow();
     m_ToolTip.Detach();
}

void CListCtrlEx::ClearAllSel()
{
     // Remove all selected rows
     POSITION Pos = GetFirstSelectedItemPosition();
     UINT State = LVIS_SELECTED|LVIS_FOCUSED;
     while(Pos)
     {
          int Idx = GetNextSelectedItem(Pos);
     	  SetItemState(Idx, ~State, State);
     }
}

